<template>
  <article class="page page-img-compress">
    <h4 class="text-center">图片压缩&格式转换</h4>
    <form class="form">
      <div class="form-group">
        <fieldset>
          <legend>压缩设置</legend>
          <div class="row">
            <div class="col-4">
              <div class="row-flex" align="center">
                <div class="row-flex-prefix">选择尺寸</div>
                <div class="row-flex-main">
                  <select class="form-control" v-model="size" @change="sizeChange">
                    <option v-for="(v,i) in sizeList" :key="i" :value="v.value">{{v.label}}</option>
                  </select>
                </div>
              </div>
            </div>
            <div class="col-4">
              <div class="row-flex" align="center">
                <div class="row-flex-prefix">宽(像素)</div>
                <div class="row-flex-main">
                  <input class="form-control" type="number" v-model.trim="width" placeholder="原比例" min="0" :disabled="size!=='-1'" />
                </div>
              </div>
            </div>
            <div class="col-4">
              <div class="row-flex" align="center">
                <div class="row-flex-prefix">高(像素)</div>
                <div class="row-flex-main">
                  <input class="form-control" type="number" v-model.trim="height" placeholder="原比例" min="0" :disabled="size!=='-1'" />
                </div>
              </div>
            </div>
          </div>
          <div class="row">
            <div class="col-4">
              <div class="row-flex" align="center">
                <div class="row-flex-prefix">质量(0-100)</div>
                <div class="row-flex-main"><input class="form-control" type="number" v-model.trim="quality" min="0" max="100" /></div>
              </div>
            </div>
            <div class="col-4">
              <div class="row-flex" align="center" style="height: 38px;">
                <div class="row-flex-prefix">允许拉伸</div>
                <div class="row-flex-main text-left">
                  <input class="form-check-input" type="checkbox" v-model="stretch" /> 是
                </div>
              </div>
            </div>
            <div class="col-4">
              <div class="row-flex" align="center">
                <div class="row-flex-prefix">图片格式</div>
                <div class="row-flex-main">
                  <select class="form-control" v-model="mimeType" placeholder="请选择图片格式">
                    <option v-for="(v,i) in mimeTypeList" :key="i" :value="v.value">{{v.label}}</option>
                  </select>
                </div>
              </div>
            </div>
          </div>
          <div class="row-flex" align="center">
            <div class="row-flex-prefix">保存位置</div>
            <div class="row-flex-main"><input class="form-control" type="text" v-model="toPath" /></div>
            <div class="row-flex-suffix"><x-button @click="setToPath">选择...</x-button></div>
          </div>
        </fieldset>
      </div>
      <div class="form-group img-list-wrapper">
        <fieldset>
          <legend>图片列表</legend>
          <div class="img-list" @drop.prevent="drop" @dragover.prevent >
            <div class="img-item" v-for="(v,i) in imagesList" :key="v.path">
              <div class="img-item-header">
                <div class="img-item-name text-ellipsis" :title="v.name">{{v.name}}</div>
                <div class="img-item-delete" @click="imgItemDelete(i)"><i class="fa fa-times"></i></div>
              </div>
              <div class="img-item-body">
                <div class="img-item-thumbnail">
                  <img :src="v.src" />
                </div>
                <div class="img-item-text">
                  <p><span class="img-item-label">源文件：</span><span class="img-item-value text-ellipsis">{{v.originSize | formatFileSize}}</span></p>
                  <p><span class="img-item-label">源尺寸：</span><span class="img-item-value text-ellipsis">{{v.originWidth}}×{{v.originHeight}}</span></p>
                  <p><span class="img-item-label">压缩后：</span><span class="img-item-value text-ellipsis text-success">{{v.size | formatFileSize}}</span></p>
                  <p><span class="img-item-label">压缩后：</span><span class="img-item-value text-ellipsis text-success">{{v.width}}×{{v.height}}</span></p>
                  <p>
                    <span><x-button size="small" type="primary" @click="saveImage(i)">保存图片</x-button></span>
                    <span><x-button size="small" @click="previewImage(i)">预览图片</x-button></span>
                  </p>
                </div>
              </div>
            </div>
            <div v-show="imagesList.length===0" class="nodata">将待处理图片拖动到此处</div>
          </div>
        </fieldset>
      </div>
      <div class="form-group text-center">
        <x-button type="default" @click="clearImagesList">清空列表</x-button>
        <x-button type="primary" @click="compressAll">全部压缩</x-button>
        <x-button type="primary" @click="saveAll">全部保存</x-button>
      </div>
    </form>
    <!-- 弹出框 -->
    <cmpMessageBox ref="cmpMessageBox" title="图片预览">
      <div slot="body" class="msgbox-body-content">
        <img :src="imagePreviewSrc" />
      </div>
    </cmpMessageBox>
  </article>
</template>

<script>
import cmpMessageBox from '@/components/cmp-message-box.vue'
const path = global.require('path')
const {remote} = global.require('electron')
const {dialog} = remote
const selfWindow= remote.getCurrentWindow()
let {formatFileSize}  = global.$jstools || {}

export default {
  name: 'img-compress',
  components: {
    cmpMessageBox
  },
  data () {
    return {
      toPath: this.$sharedObject('desktopPath'),
      size: '0',
      sizeList: [
        {label: '保持原始尺寸', value: '0', width: '', height: ''},
        {label: '证件 1 寸照', value: '1', width: 295, height: 413},
        {label: '证件 2 寸照', value: '2', width: 413, height: 626},
        {label: '自定义', value: '-1', width: '', height: ''},
      ],
      width: '',
      height: '',
      quality: 70,
      stretch: false,
      mimeType: '',
      imagesList: [],
      imagePreviewSrc: '',
      mimeTypeList: [
        {label: '原格式', value: ''},
        {label: 'JPG', value: 'image/jpeg'},
        {label: 'PNG', value: 'image/png'},
        {label: 'GIF', value: 'image/gif'},
        {label: 'BMP', value: 'image/bmp'},
        {label: 'TIF', value: 'image/tif'},
        {label: 'IFF', value: 'image/iff'},
        {label: 'ANI', value: 'image/ani'},
        {label: 'ICO', value: 'image/ico'},
      ]
    }
  },
  computed: {
    sizeDict () {
      let result = {}
      this.sizeList.forEach(v => {
          result[v.value] = v
      })
      return result
    },
    mimeTypeDict () {
      let result = {}
      this.mimeTypeList.forEach(v => {
          result[v.value] = v
      })
      return result
    }
  },
  filters: {
    formatFileSize (val) {
      return formatFileSize(val)
    }
  },
  methods: {
    // 选择尺寸
    sizeChange () {
      let {width, height} = this.sizeDict[this.size]
      this.width = width
      this.height = height
    },
    // 设置保存位置
    setToPath () {
      let filePaths = dialog.showOpenDialogSync(selfWindow, {
        title: '选择目录',
        buttonLabel:'确定',
        defaultPath: this.$sharedObject('desktopPath'),
        properties: ['openDirectory']
      })
      if (filePaths instanceof Array && filePaths.length > 0) {
        this.toPath = filePaths[0]
      }
    },
    // 拖拽添加图片
    drop (e) {
      const fileList = e.dataTransfer.files
      if (!fileList) return
      // 文件类型不存在即认为是文件夹或特殊文件
      // 取文件后缀名，必须是图片文件
      let taskList = []
      let indexList = []
      for (let i = 0; i < fileList.length; i++) {
        const file = fileList[i]
        if (!file.type) {
          return this.$toast('不支持文件夹及特殊格式文件！')
        }
        if (!/.(gif|jpg|jpeg|png|gif|jpg|png)$/i.test(file.name)) {
          return this.$toast('请选择图片文件！')
        }
        taskList.push(this.$jstools.File2dataUrl(file))
        indexList.push(i)
      }
      Promise.all(taskList).then((urls) => {
        urls.forEach((v, i) => {
          let file = fileList[indexList[i]]
          this.imagesList.push({
            name: file.name, src: v, path: file.path, mimeType: file.type, mark: '',
            originSize: file.size, originWidth: 0, originHeight: 0, imgObj: null,
            size: 0, width: 0, height: 0, canvas: null,
          })
        })
        this.loadImage()
      })
    },
    // 加载原始图片
    loadImage () {
      this.imagesList.forEach((v, i) => {
        if (v.imgObj) return
        this.$jstools.loadImage(v.src).then((imgObj) => {
          this.imagesList[i].imgObj = imgObj
          this.imagesList[i].originWidth = imgObj.width
          this.imagesList[i].originHeight = imgObj.height
        })
      })
    },
    // 删除图片
    imgItemDelete (index) {
      this.imagesList.splice(index, 1)
    },
    // 保存图片
    saveImage (index) {
      if (!this.toPath) {
        this.$toast('请设置图片保存位置')
        return
      }
      let imageItem = this.imagesList[index] || {}
      return this.compressItem(index).then((blob) => {
        if (blob) {
          this.saveBlob(blob, imageItem.name)
        } else if (imageItem.canvas) {
          imageItem.canvas.toBlob((blob) => {
            if (blob) {
              this.saveBlob(blob, imageItem.name)
            }
          }, this.mimeType || imageItem.mimeType, this.quality / 100)
        }
      })
    },
    // 将Blob数据写到本地磁盘
    saveBlob (blob, filename) {
      if (!blob || !filename) return Promise.reject('无效数据')
      return blob.arrayBuffer().then((res) => {
        if (this.mimeType) {
          let filenameArr = filename.split('.')
          if (filenameArr.length > 1) { filenameArr.pop() }
          filenameArr.push(this.mimeTypeDict[this.mimeType].label.toLowerCase())
          filename = filenameArr.join('.')
        }
        let fp = path.join(this.toPath, filename)
        this.$sharedObject('jstoolsNode').writeFile(fp, Buffer.from(res))
      }).catch((err) => {
        this.$toast('发生错误：' + err)
      })
    },
    // 预览图片
    previewImage (index) {
      let imageItem = this.imagesList[index] || {}
      this.imagePreviewSrc = ''
      this.compressItem(index).then(() => {
        this.imagePreviewSrc = imageItem.canvas.toDataURL(this.mimeType || imageItem.mimeType, this.quality / 100)
        this.$refs.cmpMessageBox && this.$refs.cmpMessageBox.show()
      })
    },
    // 清空列表
    clearImagesList () {
      this.imagesList = []
      this.imagePreviewSrc = ''
    },
    // 全部压缩
    compressAll () {
      if (this.imagesList.length <= 0) {
        this.$toast('未找到可处理的图片')
        return Promise.reject('未找到可处理的图片')
      }
      let taskList = []
      this.imagesList.forEach((v, i) => {
        taskList.push(this.compressItem(i))
      })
      return Promise.all(taskList).finally(() => {
        this.$toast('处理完成')
      })
    },
    // 单张压缩
    compressItem (index) {
      return new Promise((resolve, reject) => {
        let imageItem = this.imagesList[index] || {}
        let {width, height} = this.getOptions(index)
        let mark = `${width}_${height}_${this.stretch}_${this.quality}`
        let canvas = document.createElement('canvas')
        imageItem.width = width
        imageItem.height = height
        if (width <= 0 || height <= 0) {
          this.$toast('尺寸配置不正确')
          reject('尺寸配置不正确')
        } else if (imageItem.mark === mark) {
          canvas = imageItem.canvas
        } else {
          let ctx = canvas.getContext('2d')
          canvas.width = width
          canvas.height = height
          ctx.clearRect(0, 0, width, height)
          if (this.stretch) {
            ctx.drawImage(imageItem.imgObj, 0, 0, imageItem.originWidth, imageItem.originHeight, 0, 0, width, height)
          } else {
            let sx = (width - imageItem.originWidth) / 2
            let sy = (height - imageItem.originHeight) / 2
            ctx.drawImage(imageItem.imgObj, sx, sy)
          }
        }
        canvas.toBlob((blob) => {
          if (blob) {
            this.imagesList[index].mark = mark
            this.imagesList[index].canvas = canvas
            this.$set(this.imagesList[index], 'size', blob.size)
          } else {
            this.$set(this.imagesList[index], 'width', 0)
            this.$set(this.imagesList[index], 'height', 0)
            this.$set(this.imagesList[index], 'size', 0)
          }
          resolve(blob)
        }, this.mimeType || imageItem.mimeType, this.quality / 100)
      })
    },
    // 全部保存
    saveAll () {
      if (!this.toPath) {
        this.$toast('请设置图片保存位置')
        return
      }
      if (this.imagesList.length <= 0) {
        this.$toast('未找到可处理的图片')
        return
      }
      let taskList = []
      this.imagesList.forEach((v, i) => {
        taskList.push(this.saveImage(i))
      })
      return Promise.all(taskList).finally(() => {
        this.$toast('处理完成')
      })
    },
    // 计算要求
    getOptions (index) {
      let result = {width: 0, height: 0}
      let imageItem = this.imagesList[index] || {}
      if (this.width && this.height) {
        result.width = this.width
        result.height = this.height
      } else if (!this.width && !this.height) {
        result.width = imageItem.originWidth
        result.height = imageItem.originHeight
      } else if (this.width) {
        result.width = this.width
        result.height = Math.round((imageItem.originHeight / imageItem.originWidth) * this.width)
      } else if (this.height) {
        result.height = this.height
        result.width = Math.round((imageItem.originWidth / imageItem.originHeight) * this.height)
      }
      result.width = parseInt(result.width)
      result.height = parseInt(result.height)
      return result
    }
  }
}
</script>

<style scoped lang="less">
@import "~@/assets/css/_mixins.less";
@import "~@/assets/css/_color.less";
.row-flex {
  & + .row-flex {
    margin-top: .5em;
  }
  .row-flex-prefix {
    width: 6em;
    text-align: right;
  }
}
.form {
  height: calc(100vh - 137px);
  display: flex;
  flex-direction: column;
  .form-group {
    fieldset {
      height: 100%;
    }
  }
  .row {
    margin-bottom: .5em;
  }
  .img-list-wrapper {
    height: 0;
    flex-grow: 1;
    .img-list {
      height: calc(100% - 2em);
      overflow-y: auto;
      display: flex;
      flex-wrap: wrap;
      position: relative;
      .img-item {
        width: 380px;
        height: calc(170px + 3em);
        margin: .5em auto;
        border-radius: 8px;
        background-color: #fff;
        box-shadow: 0 0 .5em @borderGrey;
        .img-item-header {
          padding: .5em;
          display: flex;
          border-bottom: 2px solid @borderGrey;
          .img-item-name {
            width: 0;
            flex-grow: 1;
          }
          .img-item-delete {
            .flex-center;
            cursor: pointer;
            .fa-times {
              font-size: 1em;
            }
          }
        }
        .img-item-body {
          height: calc(160px + 1em);
          padding: .5em;
          display: flex;
          .img-item-thumbnail {
            width: 150px;
            margin-right: .5em;
            border: 1px solid @bgGrey;
            background-color: @bgGrey;
            img {
              display: block;
              width: 100%;
              height: 100%;
              object-fit: contain;
            }
          }
          .img-item-text {
            width: 0;
            flex-grow: 1;
            p {
              margin-bottom: 0;
              height: 30px;
              line-height: 30px;
              display: flex;
              .img-item-label {}
              .img-item-value {
                width: 0;
                flex-grow: 1;
              }
            }
          }
        }
      }
    }
  }
}
.cmp-message-box {
  .msgbox-body-content {
    height: 100%;
    .flex-center;
    img {
      max-width: 100%;
      max-height: 100%;
    }
  }
}
</style>
